% calligrapher.tex

\documentclass[oneside, 11pt]{article}

\usepackage[cm-default]{fontspec}
\usepackage{xunicode}
\usepackage{amsmath}
\usepackage{indentfirst, everb}
\usepackage{graphicx}

\defaultfontfeatures{Mapping=tex-text} 
\setromanfont{Minion Pro}

\setlength{\oddsidemargin}{0.3in}
\def\baselinestretch{1.3}

\newfontinstance\zhfont[BoldFont=Adobe Heiti Std, ItalicFont=Adobe Kaiti Std]{FZKai-Z03}
\newcommand{\zh}[1]{{\zhfont #1}}

\XeTeXlinebreaklocale "zh" 
\XeTeXlinebreakskip = 0pt plus 1pt 

\begin{document}

\title{\textbf{Calligrapher -- 基于 cairo 技术的书法创作软件}}
\author{郭涛 (200728013229035) \and 江疆 (200728013229036)}
\date{2007-12-26}
\maketitle

\renewcommand\contentsname{目录}
\renewcommand\refname{参考文献}

\tableofcontents

\includegraphics[width=4in]{cg-1.png}

\section{项目的选择与分析}

我们选择的开源项目是 cairo -- 一套跨平台的 2D 矢量图形库，cairo 是一个以 Mozilla
Public License 授权的开源软件，其开发受到了 FreeDesktop, GTK+, Firefox 这些著名
开源软件项目的大力支持，这套库也主要是为了在这些重要项目中应用而设计的。

所以，cairo 的开发者始终致力于 API 的精心设计，使之方便使用和理解。同时 cairo 的
最大优点在于高度的模块化，在模块化基础上发展起来适合 Win32 GDI, Linux X11, Mac OS X
Quartz 等不同平台的绘图技术，以达到最高质量的绘图效果与最佳的性能。

因为 cairo 的这些优点与我们希望开发的 Calligrapher 这套小软件的需求正好一致，所
以我们选择了 cairo 作为基础，也是 Calligrapher 中最核心的技术。

\subsection{cairo 的安装与配置}

如果希望安装最新版本 (考虑到可能希望对 cairo 本身做一些修改)，可以通过一下步骤。

\begin{enumerate}
\item 从 cairo 的 git 仓库获得最新版本：

\verb|$ git clone git://anongit.freedesktop.org/git/cairo|

如果没有安装 git 版本管理工具，必须先安装。

\item 安装必要的软件：

\verb|$ port install libtool pkgconfig autoconf automake libpixman|

这里使用的是 Mac OS X 下的 macports 工具安装，在其它系统下，可以使用对应的包管理
工具，比如 Ubuntu Linux 下使用\\ \verb|apt-get install build-essentials|

\item 配置编译的模块：

\begin{verbatim}
$ cd cairo
$ LIBTOOLIZE=glibtoolize ./autogen.sh
$ ./configure --enable-quartz=yes --enable-atsui=yes \
              --enable-xlib=no
\end{verbatim}

\item 编译并安装：

\verb|$ make && make install|
\end{enumerate}

如果只是用到 cairo 作为第三方库，可以简单的使用包管理工具安装：

\begin{description}
\item{Debian, Ubuntu Linux} \\
\verb|apt-get install libcairo2-dev|
\item{Arch Linux} \\
\verb|pacman -S cairo|
\item{Fedora Linux}\\
\verb|yum install cairo-devel|
\item{Windows} \\
到 \verb|ftp://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/| 
下载编译好的 win32 版本
\item{Mac OS X} \\
\verb|port install cairo +atsui +quartz|
\end{description}

\section{需求分析}

在一开始，我们本来希望对 cairo 本身的功能进行改进，但出乎我们意料的是，cairo 已
经非常好的实现了所有我们需要的功能！包括在中文支持，PDF 支持方面，都已经很好的
达到了要求，既然 cairo 已经如此成熟，为什么不尝试以 cairo 为基础开发一个新的软件
来展示其能力呢？这就是 Calligrapher 项目的起点。

以此为起点，我们列出了下面的需求：

\begin{enumerate}
\item 设计出一个程序，可以用鼠标模拟毛笔来进行书法创作。
\item 可以将用户书法作品以 pdf, svg, png 等格式导出。
\item 自动调整用户比划，使笔画粗细均匀，笔锋突出。
\item 在之前完成的基础上，设计一套 cairo 画图的 API，能让画笔宽度渐变的方式绘图。
\end{enumerate}

\section{概要设计}

\subsection{基本设计概念}

\subsubsection{毛笔特性分析}

用毛笔进行书法创作时，主要是根据线条粗细变化来进行艺术表现的。而线条的粗细主要取
决于两方面：

\begin{enumerate}

\item 运笔的速度。运笔速度快速时线条较细，运笔速度缓慢时线条较粗,并且线条的粗细是
缓慢变化的。

\item 下笔的方式。一方面下笔的力度会影响接触面的大小，下笔重时接触面积变大，下笔
轻时接触面积变小；另一方面下笔的角度会影响基础面的形状，下笔垂直时，接触面基本可
认为为圆形；下笔越倾斜，接触面越向椭圆形状靠拢。不过书法创作时，大部分时间笔杆是
垂直于纸面的，因此可以简单地认为接触面为圆形。

\end{enumerate}

\subsubsection{鼠标模拟毛笔的简要模型}

通过上面分析，我们可以发现可以简单地用鼠标来模拟毛笔。主要的模型如下：

\begin{enumerate}
\item 用鼠标的点击模拟下笔，鼠标的释放模拟提笔。
\item 鼠标的移动速度来模拟运笔速度，初步认为运笔的速度与线条粗细成反比。
\item 与"纸面"的接触面可认为为圆形，不过下笔的力度就无法通过鼠标来模拟了。
\item 由于鼠标的移动过程中，计算机只能取得一系列的不连续点和当时的画笔宽度。
\end{enumerate}

因此需要绘图过程中进行平滑处理。由于 cairo 绘图中不提供宽度渐变的绘图函数，因此
我们需要模拟一套 API，使线条的宽度能渐变。

\section{主要算法及流程}

\subsection{渐变宽度画线}

\verb|draw_line(x1, y1, d1, x2, y2, d2)| \label{func:draw}

\begin{enumerate}
\item 将绘图坐标原点移动到线段 $(x1,y1) - (x2,y2)$ 的中点。
\item 将绘图坐标旋转，是线段落在新坐标的 $x$ 轴上。
\item 将线段的轮廓用直线和圆弧画出，并用颜色填充。
\end{enumerate}
		
\subsection{主要绘图流程}

\includegraphics[width=2in]{demo1.png}
\includegraphics[width=2in]{demo2.png}

\begin{enumerate}
\item 用户按下鼠标，获得用户鼠标所在的坐标位置及当时的时间，保存起来。
\item 用户移动鼠标，触发鼠标移动事件，获得鼠标的新坐标位置及新时间，计算鼠标移动
的速度。根据鼠标移动速度计算出该点线条的宽度。
\item 用宽度渐变绘图函数画出鼠标移动产生的笔画。
\end{enumerate}

\subsection{主要模块划分}

\begin{description}

\item{绘图模块}

提供宽度渐宽的一系列绘图函数及一系列基本比划的绘图函数。

\item{事件响应模块}

对用户按键、鼠标移动等时间作出响应。

\item{导出模块}

将用户绘制图形以 pdf, svg, png 格式导出。

\end{description}

\subsection{实现细节 (源代码及说明)}

软件的安装方法详见源代码根目录下的 \verb|README| 文件。

Calligrapher 的代码是这样组织的: 

\begin{description}
\item[calligrapher/] 顶层目录。

\begin{description}
    \item[docs/] 文档。\\
        \verb|calligrapher.tex| 这份你正在阅读的文档，\LaTeX{} 格式。
    \item[jtk/] JTK 库。\\
        \verb|CMakeLists.txt|   JTK 库的 CMake 编译文件。\\
        \verb|jtk-cocoa.m|      JTK 库的 Cocoa (Mac OS X) 实现。\\
        \verb|jtk-gtk.c|        JTK 库的 GTK+ 实现。\\
        \verb|jtk.c|            JTK 库的基础实现。\\
        \verb|jtk.h|            JTK 库公开接口头文件。
    \item[src/] 主程序源代码。\\
        \verb|CMakeLists.txt|   JTK 库的 CMake 编译文件。\\
        \verb|calligrapher.c|   Calligrapher 程序主要逻辑代码。\\
        \verb|strokes.c|        Calligrapher 的画笔算法实现。
    \item[tests/] 测试用例的路径。
    \item[build/] 生成文件的路径。\\
        \verb|jtk/|   JTK 库的目标文件。\\
        \verb|main/|  主程序的目标文件 (最后执行主程序的目录)。\\
        \verb|tests/| 测试用例的目标文件。
\end{description}

\end{description}

Calligrapher 当然是一个完全开源的项目，依照 New BSD License 分发，完整的源代码
可以在  \verb|http://calligrapher.googlecode.com| 通过 Subversion 下载。

对于这份源代码，还有一些需要详细说明的地方，在下面各小节逐个进行说明。

\subsubsection{CMake}

为了考虑跨平台特性，Calligrapher 使用了 CMake 编译系统，而没有使用 GNU 编译工具
(GNU autotools)。CMake 的主要优点是支持 out-source build (编译结果可以放在与源
文件不同的任意路径下，例如在这里我们放在 \verb|build/| 目录下)，对 Windows 支持
较好，也不必生成大量冗余的文件，只需要一个 \verb|CMakeLists.txt| 文件即可。因此，
这里我们在两个子项目中用到了 CMake。

下面时用于编译的 Calligrapher 的 CMake 配置文件。

\everbinput{../src/CMakeLists.txt}

因为 Calligrapher 依赖 cairo，所以我们首先用 CMake 的 PkgConfig 模块找到 cairo
库和头文件的位置 (第 3 - 10 行)，然后在当前项目的相对路径下寻找编译好的 JTK 库
(第 7 - 11 行，JTK 库的说明见 \ref{sec:jtk} 节)。有这两个库的信息后，我们可以指
定 Calligrapher 主程序的文件 (第 19 行) 和需要额外链接的文件及查找头文件的路径
(第 21, 22, 25 行)。最后指定输出可执行文件的名称 (第 24 行)。

\subsubsection{JTK}
\label{sec:jtk}

JTK 是我们为了 Calligrapher 能在各个平台下发挥最佳图形性能，使用各自平台的原生控
件开发的跨平台图形界面接口 (GUI) 层。

在 Mac OS X 下，JTK 使用 Cocoa 编程框架 (对应 \verb|jtk-cocoa.m| 模块)，在 GTK+ 
下，JTK 使用 GTK+ 库 (对应 \verb|jtk-gtk.c|)，我们还有计划开发 win32 下使用原生
图形界面的模块。

通过 CMake 的编译脚本，我们通过调用 cmake 时的参数和系统库安装的情况判断应该使用
哪个模块进行编译，最终与 \verb|jtk.c| 通用模块的对象文件进行链接生成需要的库。

JTK 的特点在于，其图形绘制部分的代码依赖 cairo 的通用绘图上下文 (\verb|cairo_t|)，
这样可以利用 cairo 的跨平台性质实现在各个平台下绘图代码的统一，只需要由 JTK 在不
同平台下选择不同的方法获得这一上下文即可。

下面以 JTK 库的公共接口文件 (\verb|jtk.h|) 说明 JTK 库的使用。

\everbinput{../jtk/jtk.h}

首先，保证 JTK 库头文件不被重复引用 (第 3, 4 行)，然后包含所有系统下都要用到的
cairo 库的头文件 (第 6 行)。

接下来定义一些必须用到的类型，以保证其跨平台性质。其中 \verb|jtk_char| (第 8 行)
用于表示键盘输入得到的一个字符。\verb|jtk_callback_type| (第 10 - 17 行) 指应用
程序可以注册给 JTK 的回调函数的类型，分别是 \verb|JTK_DRAW| (绘图), 
\verb|JTK_MOUSE_DOWN| (鼠标按下事件), \verb|JTK_MOUSE_UP| (鼠标释放事件), 
\verb|JTK_MOUSE_DRAG| (鼠标拖拽事件), \verb|JTK_KEY_DOWN| (键盘按下事件)，
它们是应用程序与 JTK 库的主要交互手段。

\verb|jtk_event_type| (第 19 - 24 行) 是 JTK 库返回给应用程序的事件类型。用于在
应用程序段区分究竟收到了哪种类型的事件 (因为 C 语言缺乏面向对象机制，也无法在运
行时对类型进行动态判断)。

\verb|jtk_point| (第 26 - 29 行) 是对点坐标的一个定义。

\verb|jtk_event| (第 31 - 42 行) 是当回调函数被调用时 JTK 传给应用程序的主要参数，
其中包括的事件发生时间 (\verb|timestamp|) 这样的通用信息，以及根据不同事件类型
(\verb|type|) 而判断的不同数据。

\verb|jtk_callback| (第 46 行) 是一个函数指针类型，指定了 JTK 接受的回调函数的
原型。

\verb|jtk_init()| (第 50 行) 是 JTK 库的初始化函数，它由应用程序发出，要求 JTK
创建一个标题为 \verb|title|，宽度高度分别为 \verb|width| 和 \verb|height| 的窗
口，并进行其它的图形用户界面程序的必要初始化。这个窗口中的所有内容都是可以绘制
的，具体的绘制由 \verb|JTK_DRAW| 这个回调函数执行的绘制操作控制 (这个函数收到的
参数是一个 \verb|cairo_t| 上下文，所以可以使用任意 cairo 库提供的操作进行矢量图
形绘制)。

\verb|jtk_main_loop()| (第 51 行) 让应用程序进入接受图形界面事件的主循环。

\verb|jtk_set_callback()| (第 52 行) 注册一些应用程序中定义的函数给 JTK 库作为
回调函数，此后在主循环中遇到对应事件就会调用对应回调函数。

\verb|jtk_redraw()| (第 53 行) 强制要求整个图形界面重绘。

\verb|jtk_quit()| (第 54 行) 要求程序退出，让 JTK 库做必要的图形界面析构工作。

\subsubsection{Calligrapher}

有了上面的基础，下面我们可以介绍 Calligrapher 的主程序了。

首先做必要的初始化和回调函数注册工作，然后进入主循环：

\begin{everbatim}
trace_list = NULL;
init(WIDTH, HEIGHT);
jtk_init("Calligraphica", WIDTH, HEIGHT);
jtk_set_callback(JTK_DRAW, draw);
jtk_set_callback(JTK_MOUSE_DOWN, pen_down);
jtk_set_callback(JTK_MOUSE_DRAG, pen_move);
jtk_set_callback(JTK_MOUSE_UP, pen_up);
jtk_set_callback(JTK_KEY_DOWN, key_down);
jtk_main_loop();
\end{everbatim}

\verb|init()| 的目的主要是创建一个背景图像层，用于存储画好的部分。而
\verb|trace_list| 则用于存放当前这一点的轨迹列表。

\begin{everbatim}
void init(int width, int height)
{
    back_surface = 
        cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 
                                   width, height);
    back_context = cairo_create(back_surface);
}
\end{everbatim}

在主循环中，如果收到鼠标按下事件，就将鼠标点击的点和点击时间加入轨迹
列表中。

\begin{everbatim}
void pen_down(void *user)
{
    jtk_event *evt = (jtk_event *) user;
    is_writing = 1;
    point_list_append(evt->u.point, evt->timestamp, 
                      RADIUS_DEFAULT);
}
\end{everbatim}

然后鼠标开始拖拽，因此计算对应的画笔宽度，把移动的相关信息也存入轨迹
列表中。并要求重绘 (因为我们希望鼠标一边移动屏幕上一边显示出画好的部
分)：

\begin{everbatim}
void pen_move(void *user)
{
    jtk_event *evt = (jtk_event *) user;
    double radius = radius_from_event(evt);
    point_list_append(evt->u.point, evt->timestamp, 
                      radius);
    jtk_redraw();
}
\end{everbatim}

当鼠标释放，说明这一笔完成，所以我们将这一笔的轨迹画在背景层中，完成
绘制，清空鼠标轨迹列表 (这个列表永远只保存当前这一笔的轨迹，历史笔画
的轨迹都已经转换为了像素存放在一个背景图像层)。

\begin{everbatim}
void pen_up(void *user)
{
    draw_on_paper(back_context);
    point_list_clear();
    is_writing = 0;
    jtk_redraw();
}
\end{everbatim}

下面就是实际执行绘图的函数，它的工作是调用 \verb|draw_line| (参见 
\ref{func:draw} 节) 算法根据轨迹列表的数据画出这一笔。

\begin{everbatim}
void draw_on_paper(cairo_t *cr)
{
    trace *node, *prev;
    if (trace_list == NULL)
        return;
    prev = trace_list;
    node = prev->next;
    cairo_set_source_rgba(cr, 0.1, 0.1, 0.1, 0.9);
    while (node != NULL)
    {
        draw_line(cr, prev->point.x, prev->point.y, 
                  prev->radius, node->point.x, 
                  node->point.y, node->radius);
        prev = node;
        node = node->next;
    }
}
\end{everbatim}

在系统要求重绘时，执行下面的函数，将背景层的内容和当前这一笔都
画出来：

\begin{everbatim}
void draw(void *user)
{
    cairo_t *cr = (cairo_t *) user;
    cairo_save(cr);
    cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
    cairo_set_source_surface(cr, back_surface, 0, 0);
    cairo_paint(cr);
    cairo_restore(cr);
    if (is_writing)
        draw_on_paper(cr);
}
\end{everbatim}

以上，完成了对整个 Calligrapher 系统的完整介绍。

\subsection{示例}

下面是一些使用 Calligrapher 书写出来的作品效果。

\includegraphics[width=\textwidth]{xin-yun-liu-shui.png}

\end{document}

